use core::str;
use std::{fmt::Display, path::PathBuf, str::FromStr};

use anyhow::bail;
use clap::Parser;
use enum_dispatch::enum_dispatch;
use tokio::fs;

use crate::{process_text_generate, process_text_sign, process_text_verify, CmdExecutor};

use super::{verify_file, verify_path};

#[derive(Debug, Parser)]
#[enum_dispatch(CmdExecutor)]
pub enum TextSubCommand {
    #[command(name = "sign", about = " Sign a message with a private/shared key")]
    Sign(TextSignOpts),
    #[command(name = "verify", about = "Verify a message")]
    Verify(TextVerifyOpts),
    #[command(name = "generate", about = "Generate a key pair")]
    Generate(TextGenerateOpts),
}

#[derive(Debug, Parser)]
pub struct TextGenerateOpts {
    #[arg(long, default_value = "blake3",value_parser=parse_format)]
    pub format: TextSignFormat,
    #[arg(short, long,value_parser=verify_path)]
    pub output: PathBuf,
}

#[derive(Debug, Parser)]
pub struct TextSignOpts {
    /// The message to sign
    #[arg(short, long, default_value = "-")]
    pub input: String,
    /// The private key to use
    #[arg(short, long)]
    pub key: String,
    ///  sign  type
    #[arg(long, default_value = "blake3",value_parser=parse_format)]
    pub format: TextSignFormat,
}

#[derive(Debug, Parser)]
pub struct TextVerifyOpts {
    /// The message to verify
    #[arg(short, long,value_parser=verify_file ,default_value = "-")]
    pub input: String,

    /// The public key to use
    #[arg(short, long)]
    pub key: String,

    #[arg(long)]
    pub sign: String,

    #[arg(long, default_value = "blake3",value_parser=parse_format)]
    pub format: TextSignFormat,
}

#[derive(Debug, Clone, Copy)]
pub enum TextSignFormat {
    Blake3,
    Ed25519,
}

fn parse_format(s: &str) -> Result<TextSignFormat, anyhow::Error> {
    s.parse()
}

impl FromStr for TextSignFormat {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "blake3" => TextSignFormat::Blake3,
            "ed25519" => TextSignFormat::Ed25519,
            _ => bail!("Invalid format"),
        })
    }
}

impl From<TextSignFormat> for &'static str {
    fn from(value: TextSignFormat) -> Self {
        match value {
            TextSignFormat::Blake3 => "blake3",
            TextSignFormat::Ed25519 => "ed25519",
        }
    }
}
impl Display for TextSignFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", Into::<&str>::into(*self))
    }
}

impl CmdExecutor for TextSignOpts {
    async fn execute(self) -> anyhow::Result<()> {
        let signed = process_text_sign(&self.input, &self.key, self.format)?;
        println!("{}", signed);
        Ok(())
    }
}
impl CmdExecutor for TextVerifyOpts {
    async fn execute(self) -> anyhow::Result<()> {
        let ret = process_text_verify(&self.input, &self.key, &self.sign, self.format)?;
        println!("{}", ret);
        Ok(())
    }
}

impl CmdExecutor for TextGenerateOpts {
    async fn execute(self) -> anyhow::Result<()> {
        let keys = process_text_generate(self.format)?;
        if keys.len() == 1 {
            let filename = self.output.join("blake3.txt");
            fs::write(filename, &keys[0]).await?;
        } else {
            let output = &self.output;
            fs::write(output.join("ed25519.sk"), &keys[0]).await?;
            fs::write(output.join("ed25519.pk"), &keys[1]).await?;
        }
        Ok(())
    }
}
